DESperately Seeking Simulation - Part 2

Sammi Rosser, Dr Dan Chalk

Health Service Modelling Associates Programme Lambda

Recap

Entities…

Entities are the service users in your model.

Entities arrive and move through your system…

Entities go off and do different things in our model…

Generators…

Generators bring entities into being.

We might have multiple generators for different kinds of patients, if they arrive at different rates.

Example of patients arriving via ambulance and via walk-in, with clearly different arrival rates

Sinks

Example of multi-step mostly linear pathway, with patients having the possibility of leaving at multiple steps

Activities and Resources

Example of simple one-step resource use with a constrained resource

Queues

FIFO Queues

Priority-based Queue

Reneging

Balking

Jockeying

What-if Scenarios

The Power of Questions

The Code

Here’s a sample of some code for a

import simpy
import random
import math
import matplotlib.pyplot as plt
import argparse

# --------------------------
# Parse command-line arguments
# --------------------------
parser = argparse.ArgumentParser(description="Healthcare Waiting List Simulation")
parser.add_argument("--patients", type=int, default=25, help="Average new patients per week (default: 25)")
parser.add_argument("--clinicians", type=int, default=4, help="Number of clinicians (default: 4)")
parser.add_argument("--patients_per_clinician_per_week", type=int, default=5, help="Patients each clinician can see per week (default: 5)")
parser.add_argument("--duration_years", type=int, default=3, help="Simulation duration in years (default: 3)")
parser.add_argument("--initial_waitlist", type=int, default=0, help="Initial waiting list length (default: 0)")

args = parser.parse_args()

# --------------------------
# Assign from arguments
# --------------------------
patients = args.patients
clinicians = args.clinicians
patients_per_clinician_per_week = args.patients_per_clinician_per_week
sim_duration_years = args.duration_years
waiting_list_start_length = args.initial_waitlist

# --------------------------
# Tracking variables
# --------------------------
waiting_times = []
queue_lengths = []
time_points = []
patients_seen = []
all_patients = []

random.seed(42)

# --------------------------
# Simulation functions
# --------------------------
def patient(env, name, nurses, arrival_time):
    service_time = 1 / patients_per_clinician_per_week
    patient_record = {
        'name': name,
        'arrival_time': arrival_time,
        'service_start': None,
        'wait_time_weeks': None,
        'status': 'waiting'
    }
    all_patients.append(patient_record)

    with nurses.request() as request:
        yield request
        wait_time = env.now - arrival_time
        waiting_times.append(wait_time)

        patient_record['service_start'] = env.now
        patient_record['wait_time_weeks'] = wait_time
        patient_record['status'] = 'seen'

        patients_seen.append(patient_record.copy())
        yield env.timeout(service_time)

def patient_generator(env, nurses):
    for i in range(waiting_list_start_length):
        env.process(patient(env, f"Initial Patient {i+1}", nurses, 0))

    while True:
        num_arrivals = math.ceil(random.normalvariate(patients, patients * 0.2))
        num_arrivals = max(0, num_arrivals)
        for i in range(num_arrivals):
            env.process(patient(env, f"Week {math.ceil(env.now)} Patient {i+1}", nurses, env.now))
        yield env.timeout(1)

def monitor_queue(env, nurses):
    while True:
        queue_lengths.append(len(nurses.queue))
        time_points.append(env.now)
        yield env.timeout(1)

# --------------------------
# Run simulation
# --------------------------
env = simpy.Environment()
nurses_resource = simpy.Resource(env, capacity=clinicians)
env.process(patient_generator(env, nurses_resource))
env.process(monitor_queue(env, nurses_resource))
env.run(until=sim_duration_years * 52)

# --------------------------
# Plot results
# --------------------------
plt.figure(figsize=(10, 4))
plt.plot(time_points, queue_lengths, linewidth=2, color='#1f77b4')
plt.xlabel('Time (weeks)')
plt.ylabel('Waiting List Length')
plt.title('Waiting List Length Over Time')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# --------------------------
# Print summary
# --------------------------
final_waiting_list = len(nurses_resource.queue)
patients_seen_count = len(patients_seen)
avg_wait = sum(waiting_times) / len(waiting_times) if waiting_times else 0

print("\n--- Simulation Summary ---")
print(f"Final Waiting List: {final_waiting_list}")
print(f"Patients Seen: {patients_seen_count}")
print(f"Average Wait (weeks): {avg_wait:.1f}")

Running the Code

  • Ask IT for approval to install Python
  • Install the additional packages into an environment

Run it from the terminal

Web App Interfaces

Streamlit

  • Free Python-based web app framework

  • App users don’t have to install Python

  • Hosting

    • free online hosting
    • on-premisis hosting
    • browser-based running
  • Alternatives exit (Shiny, Dash)

Making our app more useful

Let’s return to our sample web app.

We’ll add

  • some extra inputs
  • some analysis of the patient wait times

Are we seeing the full picture here?

Let’s make it clearer what the wait times mean.

Introducing Variability

Comparing scenarios

How to Make a Useless Model

… and How to Avoid that.

A model is only as good as it’s spec.

Understanding the Process

  • What is the process - really?

    • I mean really really
  • Setting the model up for failure if you don’t have the experts in the process in the room from the start

  • Do you actually have the data you need to parameterise the model?

Measuring Up

  • What metrics/KPIs do you want to be able to measure?

  • How are you going to measure that the model reflects the current reality?

Scenarios

  • What do you need to be able to change?

  • Will the simplified model logic support the scenarios you want to test?

Using it for decisions

  • What do you need to be able to export?

  • Is this a one and done output, or an ongoing tool?

  • How do you assess that it made good predictions, and refine it?

Exercise: The DES Playground

You’re now going to have a go with an interactive app.

In the app, you are running a hospital.

You usually have 24 rooms available, which are divided between

With 24 rooms, your system is coping.

Due to building work, you are temporarily going to have 20 rooms available.

You need to reallocate the number of rooms available to different steps of the pathway - but is it possible to keep the hospital running smoothly still?

  1. Run the simulation with the default parameters

  2. Change some parameters

What do you like about this app?

What would you want to change?

Reflection

Exercise: Design your own Discrete Event Simulation + Interface

We’re going to take your model ideas from earlier and design a web app interface to go with them.

What can these web apps contain?

And what can they output?

Text, metric cards, static plots, interactive plots, maps, downloadable reports

Your Task

Could get them using something like draw.io or Excalidraw?

Choose a system to model from a couple of suggestions (choose some things that are quite simple), or do one of their own systems from their post-it notes earlier.